package com.filebackup.cloudapi;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.json.simple.parser.JSONParser;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.json.simple.parser.ParseException;

import com.dropbox.client2.DropboxAPI;
import com.dropbox.client2.DropboxAPI.Entry;
import com.dropbox.client2.exception.DropboxException;
import com.dropbox.client2.jsonextract.JsonExtractionException;
import com.dropbox.client2.jsonextract.JsonList;
import com.dropbox.client2.jsonextract.JsonMap;
import com.dropbox.client2.jsonextract.JsonThing;
import com.dropbox.client2.session.AccessTokenPair;
import com.dropbox.client2.session.AppKeyPair;
import com.dropbox.client2.session.WebAuthSession;
import com.filebackup.daemon.DaemonConstants;

public class DropboxApi implements ICloudApi {

	public static final String STATE_FILE = "d:\\SearchCache.json";

	private static DropboxAPI<WebAuthSession> client;

	public DropboxApi() {
		State state = State.load(STATE_FILE);
		System.out.println(state.accessToken);
		WebAuthSession was = new WebAuthSession(state.appKey,
				WebAuthSession.AccessType.APP_FOLDER);
		was.setAccessTokenPair(state.accessToken);
		client = new DropboxAPI<WebAuthSession>(was);
	}

	public void listObject(String parentFolder) {
		Entry parent = null;
		try {
			parent = client.metadata(parentFolder, 60, null, true, null);
		} catch (DropboxException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if (parent != null) {
			for (int i = 0; i < parent.contents.size(); i++) {
				Entry child = parent.contents.get(i);
				System.out.println(child.fileName());
			}
		}
		return;
	}

	public void createFolder(String path) {

		try {
				client.createFolder(path);
		} catch (DropboxException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public static final class State {
		public final AppKeyPair appKey;
		public final AccessTokenPair accessToken;
		public final Content.Folder tree;

		public State(AppKeyPair appKey, AccessTokenPair accessToken,
				Content.Folder tree) {
			this.appKey = appKey;
			this.accessToken = accessToken;
			this.tree = tree;
		}

		public String cursor;

		public void save(String fileName) {
			JSONObject jstate = new JSONObject();

			// Convert app key
			JSONArray japp = new JSONArray();
			japp.add(appKey.key);
			japp.add(appKey.secret);
			jstate.put("app_key", japp);

			// Convert access token
			JSONArray jaccess = new JSONArray();
			jaccess.add(accessToken.key);
			jaccess.add(accessToken.secret);
			jstate.put("access_token", jaccess);

			// Convert tree
			JSONObject jtree = tree.toJson();
			jstate.put("tree", jtree);

			// Convert cursor, if present.
			if (cursor != null) {
				jstate.put("cursor", cursor);
			}

			try {
				FileWriter fout = new FileWriter(fileName);
				try {
					jstate.write(fout);
				} finally {
					fout.close();
				}
			} catch (IOException ex) {
				throw die("ERROR: unable to save to state file \"" + fileName
						+ "\": " + ex.getMessage());
			}
		}

		public static State load(String fileName) {
			JsonThing j;
			try {
				FileReader fin = new FileReader(fileName);
				try {
					j = new JsonThing(new JSONParser().parse(fin));
				} catch (ParseException ex) {
					throw die("ERROR: State file \"" + fileName
							+ "\" isn't valid JSON: " + ex.getMessage());
				} finally {
					fin.close();
				}
			} catch (IOException ex) {
				throw die("ERROR: unable to load state file \"" + fileName
						+ "\": " + ex.getMessage());
			}

			try {
				JsonMap jm = j.expectMap();

				JsonList japp = jm.get("app_key").expectList();
				AppKeyPair appKey = new AppKeyPair(japp.get(0).expectString(),
						japp.get(1).expectString());

				JsonList jaccess = jm.get("access_token").expectList();
				AccessTokenPair accessToken = new AccessTokenPair(jaccess
						.get(0).expectString(), jaccess.get(1).expectString());

				JsonMap jtree = jm.get("tree").expectMap();
				Content.Folder tree = Content.Folder.fromJson(jtree);

				State state = new State(appKey, accessToken, tree);

				JsonThing jcursor = jm.getMaybe("cursor");
				if (jcursor != null) {
					state.cursor = jcursor.expectString();
				}

				return state;
			} catch (JsonExtractionException ex) {
				throw die("ERROR: State file has incorrect structure: "
						+ ex.getMessage());
			}
		}
	}

	private static RuntimeException die(String message) {
		System.err.println(message);
		return die();
	}

	private static RuntimeException die() {
		System.exit(1);
		return new RuntimeException();
	}

	public static abstract class Content {
		public abstract Object toJson();

		public static final class Folder extends Content {
			public final HashMap<String, Node> children = new HashMap<String, Node>();

			public JSONObject toJson() {
				JSONObject o = new JSONObject();
				for (Map.Entry<String, Node> c : children.entrySet()) {
					o.put(c.getKey(), c.getValue().toJson());
				}
				return o;
			}

			public static Folder fromJson(JsonMap j)
					throws JsonExtractionException {
				Folder folder = new Folder();
				for (Map.Entry<String, JsonThing> e : j) {
					folder.children
							.put(e.getKey(), Node.fromJson(e.getValue()));
				}
				return folder;
			}
		}

		public static final class File extends Content {
			public final String size;
			public final String lastModified;
			public final String clientMtime;

			public File(String size, String lastModified, String clientMtime) {
				this.size = size;
				this.lastModified = lastModified;
				this.clientMtime = clientMtime;
			}

			public JSONArray toJson() {
				JSONArray j = new JSONArray();
				j.add(size);
				j.add(lastModified);
				j.add(clientMtime);
				return j;
			}

			public static File fromJson(JsonList l)
					throws JsonExtractionException {
				return new File(l.get(0).expectString(), l.get(1)
						.expectString(), l.get(2).expectString());
			}
		}
	}

	public static final class Node {
		/**
		 * The original path of the file. We track this separately because
		 * Folder.children only contains lower-cased names.
		 */
		public String path;

		/**
		 * The node content (either Content.File or Content.Folder)
		 */
		public Content content;

		public Node(String path, Content content) {
			this.path = path;
			this.content = content;
		}

		public final JSONArray toJson() {
			JSONArray array = new JSONArray();
			array.add(path);
			array.add(content.toJson());
			return array;
		}

		public static Node fromJson(JsonThing t) throws JsonExtractionException {
			JsonList l = t.expectList();
			String path = l.get(0).expectStringOrNull();
			JsonThing jcontent = l.get(1);
			Content content;
			if (jcontent.isList()) {
				content = Content.File.fromJson(jcontent.expectList());
			} else if (jcontent.isMap()) {
				content = Content.Folder.fromJson(jcontent.expectMap());
			} else {
				throw jcontent.unexpected();
			}
			return new Node(path, content);
		}
	}

	public void uploadFile(String cloudPath, String cloudFile, String sourceFile) {
		// TODO Auto-generated method stub
		FileInputStream inputStream = null;
		try {
			File file = new File(sourceFile);
			inputStream = new FileInputStream(file);
			client.putFile(cloudPath + cloudFile, inputStream, file.length(),
					null, null);
		} catch (Exception e) {
			// User has unlinked, ask them to link again here.
			e.getStackTrace();
		} finally {
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
				}
			}
		}
	}

	public void downloadFile(String cloudPath, String cloudFile, String savePath) {
		// TODO Auto-generated method stub
		FileOutputStream outputStream = null;
		try {
			File file = new File(savePath);
			outputStream = new FileOutputStream(file);
			client.getFile(cloudPath + cloudFile, null, outputStream, null);
			
		} catch (Exception e) {
			e.getStackTrace();
		} finally {
			if (outputStream != null) {
				try {
					outputStream.close();
				} catch (IOException e) {
				}
			}
		}
	}

	public void listFiles(String cloudPath) {
		// TODO Auto-generated method stub
		
	}

	public void createPath(String cloudPath) {
		// TODO Auto-generated method stub
		createFolder(cloudPath);
	}

    public String getApiName() {
        // TODO Auto-generated method stub
        return DaemonConstants.DROPBOX_NAME;
    }

    public String getCloudPath() {
        // TODO Auto-generated method stub
        return DaemonConstants.DROPBOX_PATH;
    }
}
